Conversation
…A2, A4/A5, C) Port behavioural fixes from the js-project rewrite to the pywebview Python code, verified byte-for-byte against the real JS via a Node golden-diff harness. atlas_extracter.py: - B1: duplicate region names get unique keys (arm#2) + atlas_name field, instead of silently overwriting each other in the regions dict. - B2: parse and keep split/pad/pma and unknown region keys (extra_pairs). - B3: a region 'size:' after 'bounds:' no longer clobbers w/h. atlas_modifier.py: - A1: non-proportional replacement is treated as its own full canvas rather than padded into the original size, fixing top/right clipping. - A2: offset-aware sprite placement when padding (uses left/bottom offsets with scale instead of a flush bottom-left paste). - A4: parse_atlas/RegionInfo/repack carry atlas_name/index/split/pad/extra_pairs so metadata and duplicate names survive a repack. - A5: rebuild_atlas_text emits minimal/spec-clean output (no leading blank line, default page keys and default offsets omitted, pma only when set). atlas_converter.py: - C: unknown region keys are re-emitted during old->new conversion. A3 (multi-page repack + target_page threading) is intentionally deferred. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Port the JS repackMultiPage (atlas-modifier.js) to Python: greedy first-fit-decreasing assignment of all sprites into N pages, then shelf-pack each page. Unlike the single-page repack(), this does NOT deduplicate identical sprites. Region metadata (atlas_name/index/split/pad/extra_pairs from the phase-1 fields) is carried through, and empty pages emit a 1x1 placeholder. Backend only; not yet wired into main.py / UI (phases B and C). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e B) Add the multi-page path to main.py, mirroring the JS multi-page branch: - process_mod_image now delegates to _process_mod_multi_page when the atlas has more than one page; the single-page path is unchanged. - _process_mod_multi_page extracts every region across all pages, swaps the mod image into the selected regions, and calls repack_multi_page. - enter_modify_mode return is extended with pages/regionPages/activePage for the upcoming page-switcher UI. - save_modified writes N page PNGs (named by their original page filenames) via _save_multi_page when a multi-page merge is active. - New _merged_pages state; shutil imported for the multi-page .skel copy. Backend only (no UI yet, phase C). Verified by a headless controller test: multi-page routing, enter metadata, N-page save, and a 1-page regression (single-page path takes the old route untouched). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend (main.py): - _process_mod_multi_page now returns the MERGED region→page map and the page list (repack redistributes regions, so the enter_modify_mode map goes stale). - New get_modify_page_preview(index): serves the repacked merged page images when a multi-page merge is active, else the original loaded page images — so the switcher works both before and after merging. Frontend (ui/): - Page switcher (‹ Page X / N ›) in the modify header, shown only for >1-page atlases; prev/next swap the preview via get_modify_page_preview. - Region overlay is filtered to the visible page using the region→page map. - enter/merge refresh the switcher; exit hides and resets it. Single-page atlases are unaffected (switcher stays hidden). Verified: headless controller test (regionPages/pages + preview accessor, pre- and post-merge) and a Playwright smoke against the real ui/index.html with a mocked pywebview API (switcher visibility, indicator, prev/next enabling, preview swap, correct preview-call index, exit-hides, 1-page regression). This completes A3 (multi-page) for v1. repack_mode='all' and the relocate/repack-all helpers remain intentionally out of scope. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The multi-page path always repacks all pages (ignores the repack flag), and toggling the checkbox after a multi-page merge hit toggle_repack's None guard (_pre_repack_image is unset on the multi-page path) and showed a misleading "No merged data to repack" error. Hide #repack-options for >1-page atlases in setupModifyPages; single-page modify still shows it. Asserted in the UI smoke. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…update
Switch Windows packaging from a Nuitka onefile exe to a standalone (onedir)
build wrapped in a per-user Inno Setup installer, and rework self-update to
download and silently run that installer.
Build (.github/workflows/build_release.yml):
- Nuitka mode onefile -> standalone; rename main.exe -> AtlasToolkit.exe with a
fail-fast guard (output-file is ignored in standalone mode).
- Sign the standalone exe, build the installer with Inno Setup (ISCC), sign the
installer, and produce a portable zip of the standalone dir.
- Release assets: AtlasToolkit-Setup-x64.exe + AtlasToolkit-Windows-x64-portable.zip.
Installer (AtlasToolkit.iss, new):
- Per-user install (PrivilegesRequired=lowest, DefaultDirName={localappdata}),
fixed AppId for in-place upgrades, [Run] relaunch flagged skipifsilent so the
updater owns the silent-update relaunch. CloseApplications via Restart Manager.
Self-update (updater.py, main.py):
- Download the installer asset (find_windows_installer_asset / download_update_asset).
- restart_and_install_update now writes a detached .cmd that waits for the app to
exit, runs the installer /VERYSILENT (with /LOG), then relaunches the installed
app or relaunches with a failure notice; the cmd self-cleans. Avoids the
Restart-Manager-kills-the-helper problem of running the helper from the app exe.
- Portable builds are gated out of silent self-update (_is_installed_build) and
pointed at the releases page instead.
- Delete the obsolete onefile self_update_helper.py and its launch dispatch.
Verified on Linux: find_windows_installer_asset (mock release) and the
_build_update_script shape (silent flags, pid-wait, relaunch + failure branch,
self-delete); plus full atlas regression suite still green. The Windows build,
Inno compile, and silent-update round-trip can only be verified on Windows.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- AtlasToolkit.iss: DisableDirPage=yes so the install location matches what _is_installed_build() and the silent self-update assume; otherwise a user could install elsewhere and silently never receive auto-updates. - README: note the Windows installer/portable split and the one-time manual reinstall for pre-installer builds (their old auto-update can't find the new asset). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an opt-in "Associate .atlas files with AtlasToolkit" task to the installer. When checked, registers a per-user (HKCU via HKA) AtlasToolkit.atlas ProgID with a friendly name, the app icon, and a shell open command that passes the file as %1 — the app already opens sys.argv[1] when it ends in .atlas (startup_check). ChangesAssociations=yes refreshes the shell; entries are removed on uninstall. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… move install dir
Add the requested Inno Setup options and fix a latent install-dir collision.
Install dir collision fix (prerequisite for [InstallDelete]):
- The old DefaultDirName {localappdata}\AtlasToolkit was the SAME folder the app
uses for config.json and the update cache. Clearing it on upgrade would wipe
config and an in-progress self-update. Move install to {userpf}\AtlasToolkit
(= %LOCALAPPDATA%\Programs\AtlasToolkit), separate from the data dir, and update
_install_dir() to match so _is_installed_build() still detects installed builds.
Inno Setup additions (AtlasToolkit.iss):
- AppMutex + a named single-instance mutex created by the app at startup, so the
installer reliably detects a running instance during silent self-update.
- SetupMutex to block concurrent installer runs.
- VersionInfoVersion stamps the Setup.exe metadata.
- [InstallDelete] clears {app}\* before [Files] so stale onedir files never linger.
- [UninstallRun] removes the update cache on uninstall (keeps config.json).
- AllowNoIcons=yes (+ show the Start Menu folder page) and a Quick Launch task.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ISCC warned that "x64" is deprecated (substituting "x64os"). Use the preferred "x64compatible" — silences the warning and also allows install on ARM64 Windows via x64 emulation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…InstallDelete)
Replace the blunt "[InstallDelete] {app}\*" with the surgical Inno pattern: a
[Code] CurStepChanged(ssInstall) hook that runs the PREVIOUS version's
uninstaller silently before the new files are copied. It removes only what the
old version installed (per its uninstall log) — cleaning stale files from dropped
dependencies — while leaving app-created data (config.json, update cache) intact.
Because the uninstall is now surgical, the install-dir no longer needs to be
separate from the data dir: revert DefaultDirName and _install_dir() back to
%LOCALAPPDATA%\AtlasToolkit (matches the already-installed test build, so no
manual uninstall-first step). A wait-loop handles the uninstaller's temp-copy
relaunch so it can't delete freshly-copied files.
Needs Windows verification (untestable on Linux); failure modes are graceful
(no prior install / lookup miss -> plain overwrite).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Correct placement for extract-style mod images (shared bounds, no offsets), allow stacking multiple mods per session, fix repack-off reverting to a stale layout, and add reset plus modified-region UI cues. Co-authored-by: Cursor <cursoragent@cursor.com>
Restructure the desktop UI around a top app bar, mode toggle, relocated status/repack rows, and preview PNG export matching the copy workflow. Co-authored-by: Cursor <cursoragent@cursor.com>
Move domain, atlas ops, app bridge, and update logic into a layered package while keeping main.py as the Nuitka entry point. Co-authored-by: Cursor <cursoragent@cursor.com>
Organize the pywebview frontend into css/ and js/ layers while preserving global window APIs for onclick handlers and Python evaluate_js callbacks. Co-authored-by: Cursor <cursoragent@cursor.com>
Replace alert() for missing atlas PNGs with a modal that accepts PNG-only row-targeted drops. Confirm before discarding unsaved modify work; keep region selection across view/edit toggles. Serve modify previews via a temp-file cache instead of base64. Enable pywebview debug from source with --debug only. Co-authored-by: Cursor <cursoragent@cursor.com>
Enhance the dragover event handling in the app bridge by introducing a debounce of 500ms to prevent excessive event firing. Additionally, refactor the import of the crop_and_rotate function in the extracter module for clarity and consistency.
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
This PR delivers the v0.3.0 release by restructuring the Python code into a proper atlas_toolkit package, modularizing/redesigning the UI, and switching Windows distribution/self-update to an installer-based flow.
Changes:
- Refactors core atlas parsing/extract/modify/repack logic into
atlas_toolkit/*modules with a new app bootstrap and pywebview bridge. - Rebuilds the UI into modular JS/CSS files with new view/edit modes, multi-page support, drag/drop improvements, and update toasts.
- Updates Windows release pipeline and in-app updater to download/run an Inno Setup installer; bumps version to 0.3.0.
Reviewed changes
Copilot reviewed 50 out of 52 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Bumps lock metadata for v0.3.0 and editable source. |
| ui/style.css | Removes legacy monolithic stylesheet (replaced by CSS modules). |
| ui/index.html | New app-bar layout, mode toggle, new script/style includes. |
| ui/css/base.css | Adds global base styles and CSS variables. |
| ui/css/layout.css | Defines new app-bar + two-panel layout. |
| ui/css/components.css | Shared component styling (buttons, list, preview, toast, switcher). |
| ui/css/overlays.css | Modal/drop/context-menu/update-toast and missing-images overlay styles. |
| ui/js/state.js | Centralizes UI state variables (selection, modes, view state, multi-page). |
| ui/js/ui.js | Adds confirm modal + toast utilities and discard-mods guard. |
| ui/js/atlas-load.js | Adds load/open flow and region list refresh. |
| ui/js/regions.js | Implements region list rendering + drag selection + auto-scroll selection. |
| ui/js/preview.js | Adds preview zoom/pan and modify-mode overlay drawing. |
| ui/js/extract.js | Adds extract actions + “Save Image” for preview composition. |
| ui/js/mode.js | Adds view/edit mode toggle wiring and mode-specific UI switching. |
| ui/js/modify.js | Adds modify flow, reset/save actions, repack toggle, multi-page switcher. |
| ui/js/keyboard.js | Adds arrow-key selection navigation. |
| ui/js/drag-drop.js | Adds drag/drop overlay handling + Python callbacks. |
| ui/js/context-menu.js | Adds context menu action for copying preview image. |
| ui/js/missing-images.js | Adds missing-page-images modal with drag/drop targeting. |
| ui/js/updates.js | Adds update notification UI + download/restart flow. |
| ui/js/app.js | Bootstraps UI after pywebviewready and runs startup check. |
| self_update_helper.py | Removes legacy one-exe self-update helper (superseded by installer flow). |
| README.md | Documents installer vs portable distribution and migration note. |
| pyproject.toml | Adds build system config, bumps version to 0.3.0, configures wheel packaging. |
| AtlasToolkit.iss | Adds Inno Setup script for per-user installs + file association + upgrade behavior. |
| .github/workflows/build_release.yml | Switches CI build to Nuitka standalone, builds/signs installer + portable zip. |
| .gitignore | Stops ignoring test* and ignores CONTEXT.md. |
| atlas_toolkit/init.py | Adds package init + __version__. |
| atlas_toolkit/main.py | Adds module entrypoint calling app launcher. |
| atlas_toolkit/paths.py | Adds resource path helpers for source vs packaged runs. |
| atlas_toolkit/core/init.py | Exposes core domain model/overlay helpers. |
| atlas_toolkit/core/document.py | Adds canonical atlas parse/model/serialize seam (multi-page). |
| atlas_toolkit/core/overlay.py | Adds overlay-rect computation for modify UI rendering. |
| atlas_toolkit/core/region_ops.py | Adds extraction/crop/offset restore helpers (with page scaling). |
| atlas_toolkit/atlas/init.py | Exposes atlas operations (convert/extract/modify/repack). |
| atlas_toolkit/atlas/converter.py | Preserves unknown region keys during conversion. |
| atlas_toolkit/atlas/extracter.py | Replaces old top-level extractor with packaged AtlasProcessor. |
| atlas_toolkit/atlas/modifier.py | Refactors modifier/merge/repack flows to use new document model. |
| atlas_toolkit/atlas/repacker.py | Adds/updates repack logic incl. multi-page support + dedupe. |
| atlas_toolkit/app/init.py | Exposes app-layer primitives (session/config). |
| atlas_toolkit/app/config.py | Adds persisted config storage under per-user config dir. |
| atlas_toolkit/app/preview_cache.py | Adds file-based preview caching to avoid base64 bridge overhead. |
| atlas_toolkit/app/bridge.py | Implements pywebview JS API, drag/drop, preview/save/modify/update flows. |
| atlas_toolkit/app/launch.py | Adds app entry/flags parsing, window setup, single-instance mutex. |
| atlas_toolkit/update/init.py | Exposes update controller + version getter. |
| atlas_toolkit/update/updater.py | Updates GitHub release check + downloads installer asset. |
| atlas_toolkit/update/controller.py | Adds installer download/progress + restart-and-install orchestration. |
| atlas_extracter.py | Removes legacy monolithic extractor implementation (replaced by package modules). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+24
to
+39
| async function copyPreviewImage() { | ||
| contextMenu.classList.add("hidden"); | ||
| try { | ||
| const blob = await getPreviewPngBlob(); | ||
| if (!blob) { | ||
| showToast("No image to copy.", "error"); | ||
| return; | ||
| } | ||
|
|
||
| await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]); | ||
| showToast("Image copied to clipboard.", "success"); | ||
| } catch (e) { | ||
| console.error(e); | ||
| showToast("Failed to copy image.", "error"); | ||
| } | ||
| } |
|
|
||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
|
|
||
| <title>Atlas Extracter GUI</title> |
Comment on lines
+9
to
+13
| def app_root() -> Path: | ||
| """Project / install directory containing ``ui/``.""" | ||
| if getattr(sys, "frozen", False): | ||
| return Path(sys.executable).resolve().parent | ||
| return Path(__file__).resolve().parent.parent |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Test plan
Made with Cursor